package pl.edu.agh.ki.neuralnetwork.builder.xml;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import pl.edu.agh.ki.neuralnetwork.layer.Layer;
import pl.edu.agh.ki.neuralnetwork.layer.SimpleLayer;
import pl.edu.agh.ki.neuralnetwork.network.NeuralNetwork;
import pl.edu.agh.ki.neuralnetwork.network.NeuralNetworkImpl;
import pl.edu.agh.ki.neuralnetwork.neurons.InnerNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.Neuron;

/**
 * XML parser for neural network structure configuration file.
 * @author ravn
 *
 */
public class XmlNetworkParser extends DefaultHandler {

	private Map<String, InputNeuron> inputLayer;
	private List<Layer<InnerNeuron> > innerLayers;
	private Map<String,Neuron> neurons;
	private NeuralNetwork network;
	private InnerNeuron actual;

	private boolean isInputLayer = false;
	
	public XmlNetworkParser() {
		super();
		this.neurons = new HashMap<String,Neuron>();
	}
	
	/**
	 * 
	 * @return new network (if already built)
	 */
	public NeuralNetwork getNetwork() {
		return network;
	}
	
	public void startElement(String uri, String name, String qName,
			Attributes atts) throws SAXException {
		if(qName.equalsIgnoreCase("network")) {
		} else if(qName.equalsIgnoreCase("input")) {
			// start of input layer definition
			this.inputLayer = new HashMap<String, InputNeuron>();
			this.isInputLayer = true;
		} else if(qName.equalsIgnoreCase("inner")) {
			// inner layers section definition
			this.innerLayers = new LinkedList<Layer<InnerNeuron> >();
		} else if(qName.equalsIgnoreCase("layer"))  {
			// new inner layer definition
			this.innerLayers.add(new SimpleLayer<InnerNeuron>());
		} else if(qName.equalsIgnoreCase("neuron")) {
			// neuron definition
			if(this.isInputLayer) {
				// in input layer
				try {
					InputNeuron n = (InputNeuron) Class.forName(atts.getValue("class")).newInstance();
					this.inputLayer.put(atts.getValue("id"), n);
					this.neurons.put(atts.getValue("id"), n);
				} catch (Exception e) {
					throw new SAXException();
				} 
			} else {
				// in inner layer
				InnerNeuron n;
				try {
					n = (InnerNeuron) Class.forName(atts.getValue("class")).newInstance();
					String bias = atts.getValue("bias");
					String threshold = atts.getValue("threshold");
					// threshold is bias with - sign
					if(threshold != null) {
						Double tval = new Double(threshold);
						tval *= -1;
						n.setBias(tval);
					} 
					// set bias if defined
					if (bias != null)
						n.setBias(new Double(bias));
					this.neurons.put(atts.getValue("id"), n);
					innerLayers.get(innerLayers.size()-1).add(n);
					this.actual = n;
				} catch (Exception e) {
					throw new SAXException();
				}
			}
		} else if(qName.equalsIgnoreCase("link")) {
			// defines input for neuron
			try {
				this.actual.addInput(neurons.get(atts.getValue("fromId")), new Double(atts.getValue("weight")));
			} catch (Exception e) {
				throw new SAXException();
			}
		}
	}

	public void endElement(String uri, String name, String qName) throws SAXException {
		if(qName.equalsIgnoreCase("network")) {
			// end of network definition, we're building new network
			try {
				this.network = buildNetwork();
			} catch (Exception e) {
				throw new SAXException();
			}
		} else if(qName.equalsIgnoreCase("input")) {
			// end of input layer definition
			this.isInputLayer = false;
		} else if(qName.equalsIgnoreCase("neuron")) {
			// end of neuron definition
			this.actual = null;
		}
	}
	
	/**
	 * Builds new network based on the configuration in xml file
	 * @return new neural network
	 * @throws Exception
	 */
	private NeuralNetwork buildNetwork() throws Exception {
		Layer<InputNeuron> inputList = new SimpleLayer<InputNeuron>(inputLayer.values());
		return new NeuralNetworkImpl(inputList,innerLayers);
	}

}
